package kmi;

import java.awt.Color;
import java.util.Random;
import robocode.BulletHitEvent;
import robocode.HitByBulletEvent;
import robocode.HitWallEvent;
import robocode.HitRobotEvent;
import robocode.Robot;
import robocode.Rules;
import robocode.ScannedRobotEvent;
import robocode.WinEvent;

/**
 * A Competitive Robocode robot named GreenMachine !.
 * 
 * @author Mitchell Kupfer
 * 
 */
public class GreenMachine extends Robot {

  ScannedRobotEvent follow;
  int isWallbot = 0;
  boolean spinBot = false;
  Random rand = new Random();
  int randomDistance;
  int randomDegree;

  /**
   * The main method that runs during battle.
   */

  public void run() {
    pimpMyRide();

    setAdjustRadarForGunTurn(true);
    setAdjustGunForRobotTurn(true);
    setAdjustRadarForRobotTurn(true);

    while (true) {

      // if battling a spin bot
      if (spinBot) {
        turnRadarRight(360);
      }

      // if battling a wall bot
      else if (isWallbot > 3) {
        ahead(100);
        turnRadarRight(360);
        ahead(-100);
        turnRadarRight(360);
      }

      // Regular movements
      else {
        move();
        turnRadarRight(360);
      }
    }
  }

  /**
   * back up 100 pixels upon hitting a wall.
   * 
   * @param event is a hit wall event to to dealt with
   */
  @Override
  public void onHitWall(HitWallEvent event) {
    ahead(-100);

  }

  /**
   * Upon hitting robot fire a blast at it.
   * 
   * @param event parameter for hitting another robot.
   */
  @Override
  public void onHitRobot(HitRobotEvent event) {

    if (isWallbot < 3) {

      setBulletColor(Color.RED);

      gunTurnTo((int) getHeading() + (int) event.getBearing());

      setBulletColor(Color.RED);
      fire(Rules.MAX_BULLET_POWER);
    }
  }

  /**
   * method for when enemy is hit, fire another bullet in the same direction.
   * 
   * @param event input for the onbullethit event.
   */
  @Override
  public void onBulletHit(BulletHitEvent event) {

    if (isWallbot < 3 && !spinBot) {

      setBulletColor(Color.BLACK);

      double xPosition = event.getBullet().getX();
      double yPosition = event.getBullet().getY();

      double xValue = getX();
      double yValue = getY();

      double xSlope = xPosition - xValue;
      double ySlope = yPosition - yValue;

      double angle = Math.toDegrees(Math.atan(xSlope / ySlope));

      double heading = getGunHeading();
      double newHeading;

      // converts desired angle to how robocode represents
      // a robots heading, with 0 degrees being North.
      if (angle > 0) {

        if (xSlope < 0 && ySlope < 0) {
          angle = 180 + angle;
        }
      }

      else {

        if (xSlope > 0) {
          angle = 90 + (90 + angle);
        }

        else {
          angle = 270 + (90 + angle);
        }
      }

      if (heading < angle) {
        newHeading = angle - heading;
        turnGunRight((int) newHeading);
      }
      else {
        newHeading = heading - angle;
        turnGunLeft((int) newHeading);
      }

      fire(3);
    }
  }

  /**
   * Method override for when robot is hit fires a yellow bullet in the direction the robot was hit.
   * 
   * @param event the event parameter for hit by bullet.
   */
  @Override
  public void onHitByBullet(HitByBulletEvent event) {

    // if wallbot or spin bot do nothing
    if (isWallbot > 3 || spinBot) {
      return;
    }

    setBulletColor(Color.yellow);

    gunTurnTo(getHeading() + event.getBearing());
    fire(3);

    move();
  }

  /**
   * An on win override to do a cool victory dance.
   * 
   * @param event to deal with a win.
   */
  @Override
  public void onWin(WinEvent event) {

    setBodyColor(Color.RED);
    setBulletColor(Color.RED);
    setGunColor(Color.RED);
    setRadarColor(Color.BLACK);
    setScanColor(Color.BLACK);

    while (true) {
      ahead(15);
      back(15);
      turnRight(30);
    }

  }

  /**
   * when a robot is scanned.
   * 
   * @param event instance of a scan.
   */
  @Override
  public void onScannedRobot(ScannedRobotEvent event) {

    // updates the most recent scan event to follow
    follow = event;

    double heading = getHeading();
    double eventBearing = event.getBearing();
    double distance = event.getDistance();
    double velocity = event.getVelocity();
    double direction = event.getHeading();

    // for any modifications needed
    getPredictedLocation(distance, velocity, direction);
    
    // To deal with a wall bot
    if (isWallbot > 3) {
      chargeWallBot();
    }

    // if robot is close charge it
    else if (distance < 100) {
      charge(eventBearing, velocity, distance);
    }

    else if (follow.getName().endsWith("SpinBot")) {
      spinBot = true;
      killSpinBot();
    }

    else {

      // locate enemy
      double locationRadar = (getRadarHeading() - (getRadarHeading() - heading)) + eventBearing;
      double locationGun = (getGunHeading() - (getGunHeading() - heading)) + eventBearing;

      // point towards enemy
      radarTurnTo(locationRadar);
      gunTurnTo(locationGun);

      double power = 3.0;

      // the farther away the robot is the less power used
      for (double i = distance; i > 50 && power > .1; i = i - 50) {
        power = power - .1;
      }

      setBulletColor(Color.GREEN);

      fire(power);

      move();

    }
  }

  /**
   * used to kill the spin bot.
   */
  private void killSpinBot() {

    double bearing = follow.getBearing();

    turnTo((int) getHeading() + (int) bearing);

    ahead(follow.getDistance() / 4);
  }

  /**
   * attacks close robots and hits them.
   * 
   * @param bearing bearing of robot that hit us
   * @param velocity the velocity it was going
   * @param distance the distance away the robot was(0)
   */
  public void charge(double bearing, double velocity, double distance) {
    turnTo((int) getHeading() + (int) bearing);
    fire(3);
    ahead(distance + 100);
  }

  /**
   * ADOPTED CODE:
   * 
   * For dealing with a wall or linear robot.
   * http://www.robocoderepository.com/BotDetail.jsp?id=1520
   * 
   * Beats Walls. And that's about it. It can get better than a 90% hit-rate against walls, never
   * losing, and often never missing it over the course of a round. Stays in one place the whole
   * time. Not very useful, just demonstrates that a robot that Walls can hit 100% of the time can
   * still beat Walls. It wouldn't be hard to make a version that Walls can't hit, either.
   */
  public void chargeWallBot() {

    // get heading() needs to be getHeadingRadians somehow
    double ex =
        getX() + follow.getDistance() * Math.sin(getHeadingRadians() + follow.getBearingRadians());
    double ey =
        getY() + follow.getDistance() * Math.cos(getHeadingRadians() + follow.getBearingRadians());
    double ev = follow.getVelocity();
    double eh = follow.getHeadingRadians();
    // two turns backward, one to make up for turning the gun, the other to give me time to actually
    // fire.
    double bulletd = -22;
    // this will be true if I'm starting a turn:
    boolean turning = false;
    // if this condition comes out false, he sees me and is staying in one place firing at me.
    // Special case:
    if (ev != 0 || (Math.abs(ex - getX()) > 18 && Math.abs(ey - getY()) > 18)) {
      do {
        if (Math.min(Math.abs(eh % (Math.PI / 2)), Math.abs(Math.PI / 2 - eh % (Math.PI / 2))) > .1
            || turning) {
          turning = false;
          eh += Math.PI / 18;
        }
        else {
          // just make eh a multiple of 90 degrees if it's close to one.
          if (eh % (Math.PI / 2) < Math.PI / 4) {
            eh -= eh % (Math.PI / 2);
          }
          else {
            eh -= eh % (Math.PI / 2) - Math.PI / 2;
          }
          ex += ev * Math.sin(eh);
          ey += ev * Math.cos(eh);

          if (ev < 8) {
            ev = Math.min(ev + 1, 8);
          }
          // check for wall collision - since he's always close to the wall, I give him a little
          // extra leeway:
          if (ex < 17 || ex > getBattleFieldWidth() - 17 || ey < 17
              || ey > getBattleFieldHeight() - 17) {
            // hit the wall:
            ev = 0;
            // put myself back into the playing field:
            ex = Math.max(18, Math.min(getBattleFieldWidth() - 18, ex));
            ey = Math.max(18, Math.min(getBattleFieldHeight() - 18, ey));
            // initiate turn:
            turning = true;
          }
        }
        bulletd += 11;
      }
      while (bulletd < distance(getX(), getY(), ex, ey));
      turnGunRightRadians(normalize(Math.atan2(ex - getX(), ey - getY()) - getGunHeadingRadians()));
      fire(3);
    }
  }

  /**
   * turns the gun right these radians.
   * 
   * @param normalize in degrees the distance we want to turn gun right, made for chargewallbot.
   */
  private void turnGunRightRadians(double normalize) {
    double degree = Math.toDegrees(normalize);
    turnGunRight(degree);
  }

  /**
   * get heading of my robot is radians.
   * 
   * @return a radians representation of the robots heading.
   */
  private double getHeadingRadians() {
    return Math.toRadians(getHeading());
  }

  /**
   * get my robot gun heading in radians.
   * 
   * @return returns the gun heading converted to radians.
   */
  private double getGunHeadingRadians() {
    return Math.toRadians(getGunHeading());
  }

  /**
   * adopted method from wallskiller.
   * 
   * @param angle adapted from wallskiller code.
   * @return adapted from wallskiller code.
   */
  private double normalize(double angle) {
    return Math.atan2(Math.sin(angle), Math.cos(angle));
  }

  /**
   * adopted method from walskiller.
   * 
   * @param x1 x cooridnate of robot.
   * @param y1 y cooridnate of robot.
   * @param x2 x2 cooridnate of robot.
   * @param y2 y2 cooridnate of robot.
   * @return the distance between the robots.
   */
  private double distance(double x1, double y1, double x2, double y2) {
    return Math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
  }

  /**
   * Makes the Robot move randomly.
   */
  public void move() {

    randomDegree = rand.nextInt(300);

    randomDistance = rand.nextInt(125) + 75;

    turnTo(randomDegree);
    ahead(randomDistance);

  }

  /**
   * predicts the future location of the robot and returns the adjustment degree.
   * 
   * @param distance distance from the robot.
   * @param velocity how fast the robot is going.
   * @param direction In what direction robot is going.
   */
  public void getPredictedLocation(double distance, double velocity, double direction) {

    if (follow.getName().endsWith("Corners")) {
      return;
    }

    if (follow.getHeading() == 90 || follow.getHeading() == 180 || follow.getHeading() == 270
        || follow.getHeading() == 0) {
      
      isWallbot++;
    }
  }

  /**
   * Turns the radar to the given degree.
   * 
   * @param degree to which the radar should be turned.
   */
  public void radarTurnTo(double degree) {

    double heading = getRadarHeading();

    if (heading < degree) {

      if (degree - heading > 180) {
        turnRadarLeft(heading + (360 - degree));
      }

      else {
        turnRadarRight(degree - heading);
      }

    }

    else {

      if (heading - degree > 180) {
        turnRadarRight(degree + (360 - heading));
      }

      else {
        turnRadarLeft(heading - degree);
      }
    }

  }

  /**
   * Turns the gun to the given degree.
   * 
   * @param degree to which the gun should be turned.
   */
  public void gunTurnTo(double degree) {

    double heading = getGunHeading();

    if (heading < degree) {

      if (degree - heading > 180) {
        turnGunLeft(heading + (360 - degree));
      }

      else {
        turnGunRight(degree - heading);
      }
    }

    else {

      if (heading - degree > 180) {
        turnGunRight(degree + (360 - heading));
      }

      else {
        turnGunLeft(heading - degree);
      }
    }

  }

  /**
   * Turns to the degree given.
   * 
   * @param degree to which the robot should be turned.
   */
  public void turnTo(int degree) {

    double heading = getHeading();

    if (heading < degree) {

      if (degree - heading > 180) {
        turnLeft(heading + (360 - degree));
      }

      else {
        turnRight(degree - heading);
      }
    }

    else {
      if (heading - degree > 180) {
        turnRight(degree + (360 - heading));
      }

      else {
        turnLeft(heading - degree);
      }
    }
  }

  /**
   * Custom paint jobs.
   */
  public void pimpMyRide() {

    setBodyColor(Color.GREEN);
    setBulletColor(Color.GREEN);
    setGunColor(Color.GREEN);
    setRadarColor(Color.GREEN);
    setScanColor(Color.GREEN);
  }
}
